/*
fork与vfork的区别
1.fork要拷贝父进程的进程环境；而vfork则不需要完全拷贝父进程的进程环境，在子进程没有调用exec和exit之前，子进程与父进程共享进程环境，相当于线程的概念，此时父进程阻塞等待。
2.vfork保证子进程先运行，在它调用exec或exit之后父进程才可能被调度运行。如果在调用这两个函数之前子进程依赖于父进程的进一步动作，则会导致死锁。

为什么会有vfork呢?
因为fork创建一个子进程时，将会创建一个新的地址空间，并且拷贝父进程的资源，然后将会有两种行为：
1.执行从父进程那里拷贝过来的代码段
2.调用一个exec执行一个新的代码段

当进程调用exec函数时，一个新程序替换了当前进程的正文，数据，堆和栈段。这样，前面的拷贝工作就是白费力气了。
vfork是为了避免这种浪费。vfork并不复制父进程的进程环境，子进程在父进程的地址空间中运行，所以子进程不能进行写操作，父进程继续运行时会使用子进程更新过的数据
子进程未调用exec或exit时，父进程只能阻塞等待，一旦儿子执行了exec或者exit后，。

因此，如果创建子进程是为了调用exec执行一个新的程序的时候，应该使用vfork
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

void usage(char* prg)
{
	printf("Usage: %s fork|vfork [noexe]\n", prg);
}

void exec_ls()
{
	char* exe_argv[3];
	exe_argv[0] = "ls";
	exe_argv[1] = "-l";
	exe_argv[2] = NULL;
	int ret = execv("/bin/null", exe_argv);
	if (ret == -1)
	{
		perror("excv");
	}

	// 当exec命令错误，vfork不调用exit父进程会段错误
	exit(0);
}

int main(int argc, char** argv)
{
	if (argc < 2)
	{
		usage(argv[0]);
		return 0;
	}

	int var = 0;

	int mode = 0; // default fork
	if (strcmp(argv[1], "vfork") == 0)
		mode = 1;
	else if (strcmp(argv[1], "fork") == 0)
		mode = 0;
	else
	{
		usage(argv[0]);
		return 0;
	}

	int noexe = argc > 2 ? 1 : 0;

	pid_t cpid;
	cpid = mode ? vfork() : fork();
	if (cpid == 0)
	{
		var = 1;
		printf("%s child, var %d\n", argv[1], var);
		if (!noexe)
			exec_ls();
		else  // vfork时，不执行exec或exit，父进程会段错误
		{
			printf("vfork without exec/exit, father process may segfault\n");
		}
	}
	else
	{
		// vfork时，父进程获取的是修改后的var
		printf("%s father, var %d\n", argv[1], var);
	}

	return 0;
}

